/*
* Copyright 2012-present Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License. You may obtain
* a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package com.facebook.buck.json;
import com.facebook.buck.event.BuckEventBus;
import com.facebook.buck.event.ConsoleEvent;
import com.facebook.buck.event.PerfEventId;
import com.facebook.buck.event.SimplePerfEvent;
import com.facebook.buck.io.PathOrGlobMatcher;
import com.facebook.buck.io.ProjectWatch;
import com.facebook.buck.io.WatchmanDiagnostic;
import com.facebook.buck.io.WatchmanDiagnosticEvent;
import com.facebook.buck.log.Logger;
import com.facebook.buck.rules.Description;
import com.facebook.buck.rules.coercer.TypeCoercerFactory;
import com.facebook.buck.util.InputStreamConsumer;
import com.facebook.buck.util.MoreCollectors;
import com.facebook.buck.util.MoreThrowables;
import com.facebook.buck.util.ObjectMappers;
import com.facebook.buck.util.ProcessExecutor;
import com.facebook.buck.util.ProcessExecutorParams;
import com.facebook.buck.util.Threads;
import com.facebook.buck.util.concurrent.AssertScopeExclusiveAccess;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonParser;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Preconditions;
import com.google.common.base.Strings;
import com.google.common.base.Supplier;
import com.google.common.base.Suppliers;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Maps;
import com.google.common.io.CountingInputStream;
import java.io.BufferedOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
import java.util.concurrent.atomic.AtomicLong;
import javax.annotation.Nullable;
/**
* Delegates to buck.py for parsing of buck build files. Constructed on demand for the parsing phase
* and must be closed afterward to free up resources.
*/
public class ProjectBuildFileParser implements AutoCloseable {
private static final String PYTHONPATH_ENV_VAR_NAME = "PYTHONPATH";
private static final Logger LOG = Logger.get(ProjectBuildFileParser.class);
private final ImmutableMap<String, String> environment;
@Nullable private BuckPythonProgram buckPythonProgram;
private Supplier<Path> rawConfigJson;
private Supplier<Path> ignorePathsJson;
@Nullable private ProcessExecutor.LaunchedProcess buckPyProcess;
@Nullable private CountingInputStream buckPyProcessInput;
@Nullable private JsonGenerator buckPyProcessJsonGenerator;
@Nullable private JsonParser buckPyProcessJsonParser;
private final ProjectBuildFileParserOptions options;
private final TypeCoercerFactory typeCoercerFactory;
private final BuckEventBus buckEventBus;
private final ProcessExecutor processExecutor;
private final AssertScopeExclusiveAccess assertSingleThreadedParsing;
private boolean isInitialized;
private boolean isClosed;
private boolean enableProfiling;
@Nullable private FutureTask<Void> stderrConsumerTerminationFuture;
@Nullable private Thread stderrConsumerThread;
@Nullable private ProjectBuildFileParseEvents.Started projectBuildFileParseEventStarted;
public ProjectBuildFileParser(
final ProjectBuildFileParserOptions options,
final TypeCoercerFactory typeCoercerFactory,
ImmutableMap<String, String> environment,
BuckEventBus buckEventBus,
ProcessExecutor processExecutor) {
this.buckPythonProgram = null;
this.options = options;
this.typeCoercerFactory = typeCoercerFactory;
this.environment = environment;
this.buckEventBus = buckEventBus;
this.processExecutor = processExecutor;
this.assertSingleThreadedParsing = new AssertScopeExclusiveAccess();
this.rawConfigJson =
Suppliers.memoize(
() -> {
try {
Path rawConfigJson1 = Files.createTempFile("raw_config", ".json");
Files.createDirectories(rawConfigJson1.getParent());
try (OutputStream output =
new BufferedOutputStream(Files.newOutputStream(rawConfigJson1))) {
ObjectMappers.WRITER.writeValue(output, options.getRawConfig());
}
return rawConfigJson1;
} catch (IOException e) {
throw new RuntimeException(e);
}
});
this.ignorePathsJson =
Suppliers.memoize(
() -> {
try {
Path ignorePathsJson1 = Files.createTempFile("ignore_paths", ".json");
Files.createDirectories(ignorePathsJson1.getParent());
try (OutputStream output =
new BufferedOutputStream(Files.newOutputStream(ignorePathsJson1))) {
ObjectMappers.WRITER.writeValue(
output,
options
.getIgnorePaths()
.stream()
.map(PathOrGlobMatcher::getPathOrGlob)
.collect(MoreCollectors.toImmutableList()));
}
return ignorePathsJson1;
} catch (IOException e) {
throw new RuntimeException(e);
}
});
}
public void setEnableProfiling(boolean enableProfiling) {
ensureNotClosed();
ensureNotInitialized();
this.enableProfiling = enableProfiling;
}
@VisibleForTesting
public boolean isClosed() {
return isClosed;
}
private void ensureNotClosed() {
Preconditions.checkState(!isClosed);
}
private void ensureNotInitialized() {
Preconditions.checkState(!isInitialized);
}
/**
* Initialization on demand moves around the performance impact of creating the Python interpreter
* to when parsing actually begins. This makes it easier to attribute this time to the actual
* parse phase.
*/
@VisibleForTesting
public void initIfNeeded() throws IOException {
ensureNotClosed();
if (!isInitialized) {
init();
isInitialized = true;
}
}
/** Initialize the parser, starting buck.py. */
private void init() throws IOException {
projectBuildFileParseEventStarted = new ProjectBuildFileParseEvents.Started();
buckEventBus.post(projectBuildFileParseEventStarted);
try (SimplePerfEvent.Scope scope =
SimplePerfEvent.scope(buckEventBus, PerfEventId.of("ParserInit"))) {
ImmutableMap.Builder<String, String> pythonEnvironmentBuilder = ImmutableMap.builder();
// Strip out PYTHONPATH. buck.py manually sets this to include only nailgun. We don't want
// to inject nailgun into the parser's PYTHONPATH, so strip that value out.
// If we wanted to pass on some environmental PYTHONPATH, we would have to do some actual
// merging of this and the BuckConfig's python module search path.
pythonEnvironmentBuilder.putAll(
Maps.filterKeys(environment, k -> !PYTHONPATH_ENV_VAR_NAME.equals(k)));
if (options.getPythonModuleSearchPath().isPresent()) {
pythonEnvironmentBuilder.put(
PYTHONPATH_ENV_VAR_NAME, options.getPythonModuleSearchPath().get());
}
ImmutableMap<String, String> pythonEnvironment = pythonEnvironmentBuilder.build();
ProcessExecutorParams params =
ProcessExecutorParams.builder()
.setCommand(buildArgs())
.setEnvironment(pythonEnvironment)
.build();
LOG.debug(
"Starting buck.py command: %s environment: %s",
params.getCommand(), params.getEnvironment());
buckPyProcess = processExecutor.launchProcess(params);
LOG.debug("Started process %s successfully", buckPyProcess);
buckPyProcessInput = new CountingInputStream(buckPyProcess.getInputStream());
buckPyProcessJsonGenerator = ObjectMappers.createGenerator(buckPyProcess.getOutputStream());
// We have to wait to create the JsonParser until after we write our
// first request, because Jackson "helpfully" synchronously reads
// from the InputStream trying to detect whether the encoding is
// UTF-8 or UTF-16 as soon as you create a JsonParser:
//
// https://git.io/vSgnA
//
// Since buck.py doesn't write any data until after it receives
// a query, creating the JsonParser here would hang indefinitely.
InputStream stderr = buckPyProcess.getErrorStream();
InputStreamConsumer stderrConsumer =
new InputStreamConsumer(
stderr,
(InputStreamConsumer.Handler)
line ->
buckEventBus.post(
ConsoleEvent.warning("Warning raised by BUCK file parser: %s", line)));
stderrConsumerTerminationFuture = new FutureTask<>(stderrConsumer);
stderrConsumerThread =
Threads.namedThread(
ProjectBuildFileParser.class.getSimpleName(), stderrConsumerTerminationFuture);
stderrConsumerThread.start();
}
}
private ImmutableList<String> buildArgs() throws IOException {
// Invoking buck.py and read JSON-formatted build rules from its stdout.
ImmutableList.Builder<String> argBuilder = ImmutableList.builder();
argBuilder.add(options.getPythonInterpreter());
// Ask python to unbuffer stdout so that we can coordinate based on the output as it is
// produced.
argBuilder.add("-u");
argBuilder.add(getPathToBuckPy(options.getDescriptions()).toString());
if (enableProfiling) {
argBuilder.add("--profile");
}
if (options.getAllowEmptyGlobs()) {
argBuilder.add("--allow_empty_globs");
}
if (options.getUseWatchmanGlob()) {
argBuilder.add("--use_watchman_glob");
}
if (options.getWatchmanGlobStatResults()) {
argBuilder.add("--watchman_glob_stat_results");
}
if (options.getWatchmanUseGlobGenerator()) {
argBuilder.add("--watchman_use_glob_generator");
}
if (options.getWatchman().getTransportPath().isPresent()) {
argBuilder.add(
"--watchman_socket_path",
options.getWatchman().getTransportPath().get().toAbsolutePath().toString());
}
if (options.getWatchmanQueryTimeoutMs().isPresent()) {
argBuilder.add(
"--watchman_query_timeout_ms", options.getWatchmanQueryTimeoutMs().get().toString());
}
if (options.getUseMercurialGlob()) {
argBuilder.add("--use_mercurial_glob");
}
// Add the --build_file_import_whitelist flags.
for (String module : options.getBuildFileImportWhitelist()) {
argBuilder.add("--build_file_import_whitelist");
argBuilder.add(module);
}
argBuilder.add("--project_root", options.getProjectRoot().toAbsolutePath().toString());
for (ImmutableMap.Entry<String, Path> entry : options.getCellRoots().entrySet()) {
argBuilder.add("--cell_root", entry.getKey() + "=" + entry.getValue());
}
argBuilder.add("--cell_name", options.getCellName());
argBuilder.add("--build_file_name", options.getBuildFileName());
// Tell the parser not to print exceptions to stderr.
argBuilder.add("--quiet");
// Add the --include flags.
for (String include : options.getDefaultIncludes()) {
argBuilder.add("--include");
argBuilder.add(include);
}
// Add all config settings.
argBuilder.add("--config", rawConfigJson.get().toString());
// Add ignore paths.
argBuilder.add("--ignore_paths", ignorePathsJson.get().toString());
return argBuilder.build();
}
/**
* Collect all rules from a particular build file.
*
* @param buildFile should be an absolute path to a build file. Must have rootPath as its prefix.
*/
public List<Map<String, Object>> getAll(Path buildFile, AtomicLong processedBytes)
throws BuildFileParseException, InterruptedException {
ImmutableList<Map<String, Object>> result = getAllRulesAndMetaRules(buildFile, processedBytes);
// Strip out the __includes, __configs, and __env meta rules, which are the last rules.
return Collections.unmodifiableList(result.subList(0, result.size() - 3));
}
/**
* Collect all rules from a particular build file, along with meta rules about the rules, for
* example which build files the rules depend on.
*
* @param buildFile should be an absolute path to a build file. Must have rootPath as its prefix.
*/
public ImmutableList<Map<String, Object>> getAllRulesAndMetaRules(
Path buildFile, AtomicLong processedBytes)
throws BuildFileParseException, InterruptedException {
try {
return getAllRulesInternal(buildFile, processedBytes);
} catch (IOException e) {
LOG.warn(e, "Error getting all rules for %s", buildFile);
MoreThrowables.propagateIfInterrupt(e);
throw BuildFileParseException.createForBuildFileParseError(buildFile, e);
}
}
@VisibleForTesting
protected ImmutableList<Map<String, Object>> getAllRulesInternal(
Path buildFile, AtomicLong processedBytes) throws IOException, BuildFileParseException {
ensureNotClosed();
initIfNeeded();
// Check isInitialized implications (to avoid Eradicate warnings).
Preconditions.checkNotNull(buckPyProcess);
Preconditions.checkNotNull(buckPyProcessInput);
long alreadyReadBytes = buckPyProcessInput.getCount();
ParseBuckFileEvent.Started parseBuckFileStarted = ParseBuckFileEvent.started(buildFile);
buckEventBus.post(parseBuckFileStarted);
ImmutableList<Map<String, Object>> values = ImmutableList.of();
Optional<String> profile = Optional.empty();
try (AssertScopeExclusiveAccess.Scope scope = assertSingleThreadedParsing.scope()) {
Path cellPath = options.getProjectRoot().toAbsolutePath();
String watchRoot = cellPath.toString();
String projectPrefix = "";
if (options.getWatchman().getProjectWatches().containsKey(cellPath)) {
ProjectWatch projectWatch = options.getWatchman().getProjectWatches().get(cellPath);
watchRoot = projectWatch.getWatchRoot();
if (projectWatch.getProjectPrefix().isPresent()) {
projectPrefix = projectWatch.getProjectPrefix().get();
}
}
buckPyProcessJsonGenerator.writeObject(
ImmutableMap.of(
"buildFile", buildFile.toString(),
"watchRoot", watchRoot,
"projectPrefix", projectPrefix));
try {
// We disable autoflush at the ObjectMapper level for
// performance reasons, but our protocol requires us to
// flush newline-delimited JSON for each buck.py query.
buckPyProcessJsonGenerator.flush();
// I tried using MinimalPrettyPrinter.setRootValueSeparator("\n") and
// setting it on the JsonGenerator, but it doesn't seem to
// actually write a newline after each element.
buckPyProcess.getOutputStream().write('\n');
// I tried enabling JsonGenerator.Feature.FLUSH_PASSED_TO_STREAM,
// but it doesn't actually flush.
buckPyProcess.getOutputStream().flush();
} catch (IOException e) {
// https://issues.apache.org/jira/browse/EXEC-101 -- Java 8 throws
// IOException if the child process exited before writing/flushing
LOG.debug(e, "Swallowing exception on flush");
}
if (buckPyProcessJsonParser == null) {
// We have to wait to create the JsonParser until after we write our
// first request, because Jackson "helpfully" synchronously reads
// from the InputStream trying to detect whether the encoding is
// UTF-8 or UTF-16 as soon as you create a JsonParser:
//
// https://git.io/vSgnA
//
// Since buck.py doesn't write any data until after it receives
// a query, creating the JsonParser any earlier than this would
// hang indefinitely.
buckPyProcessJsonParser = ObjectMappers.createParser(buckPyProcessInput);
}
LOG.verbose("Parsing output of process %s...", buckPyProcess);
BuildFilePythonResult resultObject;
try {
resultObject = buckPyProcessJsonParser.readValueAs(BuildFilePythonResult.class);
} catch (IOException e) {
LOG.warn(e, "Parser exited while decoding JSON data");
throw e;
}
Path buckPyPath = getPathToBuckPy(options.getDescriptions());
handleDiagnostics(
buildFile, buckPyPath.getParent(), resultObject.getDiagnostics(), buckEventBus);
values = resultObject.getValues();
LOG.verbose("Got rules: %s", values);
LOG.verbose("Parsed %d rules from %s", values.size(), buildFile);
profile = resultObject.getProfile();
if (profile.isPresent()) {
LOG.debug("Profile result: %s", profile.get());
}
return values;
} finally {
long parsedBytes = buckPyProcessInput.getCount() - alreadyReadBytes;
processedBytes.addAndGet(parsedBytes);
buckEventBus.post(
ParseBuckFileEvent.finished(parseBuckFileStarted, values, parsedBytes, profile));
}
}
private static void handleDiagnostics(
Path buildFile,
Path buckPyDir,
List<Map<String, Object>> diagnosticsList,
BuckEventBus buckEventBus)
throws IOException, BuildFileParseException {
for (Map<String, Object> diagnostic : diagnosticsList) {
String level = (String) diagnostic.get("level");
String message = (String) diagnostic.get("message");
String source = (String) diagnostic.get("source");
if (level == null || message == null) {
throw new IOException(
String.format(
"Invalid diagnostic(level=%s, message=%s, source=%s)", level, message, source));
}
if (source != null && source.equals("watchman")) {
handleWatchmanDiagnostic(buildFile, level, message, buckEventBus);
} else {
String header;
if (source != null) {
header = buildFile + " (" + source + ")";
} else {
header = buildFile.toString();
}
switch (level) {
case "debug":
LOG.debug("%s: %s", header, message);
break;
case "info":
LOG.info("%s: %s", header, message);
break;
case "warning":
LOG.warn("Warning raised by BUCK file parser for file %s: %s", header, message);
buckEventBus.post(
ConsoleEvent.warning("Warning raised by BUCK file parser: %s", message));
break;
case "error":
LOG.warn("Error raised by BUCK file parser for file %s: %s", header, message);
buckEventBus.post(ConsoleEvent.severe("Error raised by BUCK file parser: %s", message));
break;
case "fatal":
LOG.warn("Fatal error raised by BUCK file parser for file %s: %s", header, message);
Object exception = diagnostic.get("exception");
throw BuildFileParseException.createForBuildFileParseError(
buildFile, createParseException(buildFile, buckPyDir, message, exception));
default:
LOG.warn(
"Unknown diagnostic (level %s) raised by BUCK file parser for build file %s: %s",
level, buildFile, message);
break;
}
}
}
}
private static Optional<BuildFileSyntaxError> parseSyntaxError(Map<String, Object> exceptionMap) {
String type = (String) exceptionMap.get("type");
if ("SyntaxError".equals(type)) {
return Optional.of(
BuildFileSyntaxError.of(
Paths.get((String) Preconditions.checkNotNull(exceptionMap.get("filename"))),
(Number) Preconditions.checkNotNull(exceptionMap.get("lineno")),
Optional.ofNullable((Number) exceptionMap.get("offset")),
(String) Preconditions.checkNotNull(exceptionMap.get("text"))));
} else {
return Optional.empty();
}
}
@SuppressWarnings("unchecked")
private static ImmutableList<BuildFileParseExceptionStackTraceEntry> parseStackTrace(
Map<String, Object> exceptionMap) {
List<Map<String, Object>> traceback =
(List<Map<String, Object>>) Preconditions.checkNotNull(exceptionMap.get("traceback"));
ImmutableList.Builder<BuildFileParseExceptionStackTraceEntry> stackTraceBuilder =
ImmutableList.builder();
for (Map<String, Object> tracebackItem : traceback) {
stackTraceBuilder.add(
BuildFileParseExceptionStackTraceEntry.of(
Paths.get((String) Preconditions.checkNotNull(tracebackItem.get("filename"))),
(Number) Preconditions.checkNotNull(tracebackItem.get("line_number")),
(String) Preconditions.checkNotNull(tracebackItem.get("function_name")),
(String) Preconditions.checkNotNull(tracebackItem.get("text"))));
}
return stackTraceBuilder.build();
}
@VisibleForTesting
static BuildFileParseExceptionData parseExceptionData(Map<String, Object> exceptionMap) {
return BuildFileParseExceptionData.of(
(String) Preconditions.checkNotNull(exceptionMap.get("type")),
(String) Preconditions.checkNotNull(exceptionMap.get("value")),
parseSyntaxError(exceptionMap),
parseStackTrace(exceptionMap));
}
private static String formatStackTrace(
Path buckPyDir, ImmutableList<BuildFileParseExceptionStackTraceEntry> stackTrace) {
StringBuilder formattedTraceback = new StringBuilder();
for (BuildFileParseExceptionStackTraceEntry entry : stackTrace) {
if (entry.getFileName().getParent().equals(buckPyDir)) {
// Skip stack trace entries for buck.py itself
continue;
}
String location;
if (entry.getFunctionName().equals("<module>")) {
location = "";
} else {
location = String.format(", in %s", entry.getFunctionName());
}
formattedTraceback.append(
String.format(
" File \"%s\", line %s%s\n %s\n",
entry.getFileName(), entry.getLineNumber(), location, entry.getText()));
}
return formattedTraceback.toString();
}
@SuppressWarnings("unchecked")
private static IOException createParseException(
Path buildFile, Path buckPyDir, String message, @Nullable Object exception) {
if (!(exception instanceof Map<?, ?>)) {
return new IOException(message);
} else {
Map<String, Object> exceptionMap = (Map<String, Object>) exception;
BuildFileParseExceptionData exceptionData = parseExceptionData(exceptionMap);
LOG.debug("Received exception from buck.py parser: %s", exceptionData);
Optional<BuildFileSyntaxError> syntaxErrorOpt = exceptionData.getSyntaxError();
if (syntaxErrorOpt.isPresent()) {
BuildFileSyntaxError syntaxError = syntaxErrorOpt.get();
String errorMsg = "";
if (buildFile.equals(syntaxError.getFileName())) {
// BuildFileParseException will include the filename
errorMsg += String.format("Syntax error on line %s", syntaxError.getLineNumber());
} else {
// Parse error was in some other file included by the build file
errorMsg +=
String.format(
"Syntax error in %s\nLine %s",
syntaxError.getFileName(), syntaxError.getLineNumber());
}
if (syntaxError.getOffset().isPresent()) {
errorMsg += String.format(", column %s", syntaxError.getOffset().get());
}
errorMsg += ":\n" + syntaxError.getText();
if (syntaxError.getOffset().isPresent()) {
errorMsg += Strings.padStart("^", syntaxError.getOffset().get().intValue(), ' ');
}
return new IOException(errorMsg);
} else if (exceptionData.getType().equals("IncorrectArgumentsException")) {
return new IOException(message);
} else {
String formattedStackTrace = formatStackTrace(buckPyDir, exceptionData.getStackTrace());
return new IOException(
String.format(
"%s: %s\nCall stack:\n%s",
exceptionData.getType(), exceptionData.getValue(), formattedStackTrace));
}
}
}
private static void handleWatchmanDiagnostic(
Path buildFile, String level, String message, BuckEventBus buckEventBus) throws IOException {
WatchmanDiagnostic.Level watchmanDiagnosticLevel;
switch (level) {
// Watchman itself doesn't issue debug or info, but in case
// engineers hacking on stuff add calls, let's log them
// then return.
case "debug":
LOG.debug("%s (watchman): %s", buildFile, message);
return;
case "info":
LOG.info("%s (watchman): %s", buildFile, message);
return;
case "warning":
watchmanDiagnosticLevel = WatchmanDiagnostic.Level.WARNING;
break;
case "error":
watchmanDiagnosticLevel = WatchmanDiagnostic.Level.ERROR;
break;
case "fatal":
throw new IOException(String.format("%s: %s", buildFile, message));
default:
throw new RuntimeException(
String.format(
"Unrecognized watchman diagnostic level: %s (message=%s)", level, message));
}
WatchmanDiagnostic watchmanDiagnostic = WatchmanDiagnostic.of(watchmanDiagnosticLevel, message);
buckEventBus.post(new WatchmanDiagnosticEvent(watchmanDiagnostic));
}
@Override
@SuppressWarnings("PMD.EmptyCatchBlock")
public void close() throws BuildFileParseException, InterruptedException, IOException {
if (isClosed) {
return;
}
try {
if (isInitialized) {
// Check isInitialized implications (to avoid Eradicate warnings).
Preconditions.checkNotNull(buckPyProcess);
// Allow buck.py to terminate gracefully.
if (buckPyProcessJsonGenerator != null) {
try {
LOG.debug("Closing buck.py process stdin");
// Closing the JSON generator has the side effect of closing stdin,
// which lets buck.py terminate gracefully.
buckPyProcessJsonGenerator.close();
} catch (IOException e) {
// Safe to ignore since we've already flushed everything we wanted
// to write.
} finally {
buckPyProcessJsonGenerator = null;
}
}
if (buckPyProcessJsonParser != null) {
try {
buckPyProcessJsonParser.close();
} catch (IOException e) {
} finally {
buckPyProcessJsonParser = null;
}
}
if (stderrConsumerThread != null) {
stderrConsumerThread.join();
stderrConsumerThread = null;
try {
Preconditions.checkNotNull(stderrConsumerTerminationFuture).get();
} catch (ExecutionException e) {
Throwable cause = e.getCause();
if (cause instanceof IOException) {
throw (IOException) cause;
} else {
throw new RuntimeException(e);
}
}
stderrConsumerTerminationFuture = null;
}
LOG.debug("Waiting for process %s to exit...", buckPyProcess);
ProcessExecutor.Result result = processExecutor.waitForLaunchedProcess(buckPyProcess);
if (result.getExitCode() != 0) {
LOG.warn(result.getMessageForUnexpectedResult(buckPyProcess.toString()));
throw BuildFileParseException.createForUnknownParseError(
result.getMessageForResult("Parser did not exit cleanly"));
}
LOG.debug("Process %s exited cleanly.", buckPyProcess);
try {
synchronized (this) {
if (buckPythonProgram != null) {
buckPythonProgram.close();
}
}
} catch (IOException e) {
// Eat any exceptions from deleting the temporary buck.py file.
}
}
} finally {
if (isInitialized) {
buckEventBus.post(
new ProjectBuildFileParseEvents.Finished(
Preconditions.checkNotNull(projectBuildFileParseEventStarted)));
}
isClosed = true;
}
}
private synchronized Path getPathToBuckPy(ImmutableSet<Description<?>> descriptions)
throws IOException {
if (buckPythonProgram == null) {
buckPythonProgram = BuckPythonProgram.newInstance(typeCoercerFactory, descriptions);
}
return buckPythonProgram.getExecutablePath();
}
}